import sys
import time
import win32api
import win32con

import Page
import allure
import urllib.parse
import requests
import sys, json
from time import sleep
from pymouse import PyMouse

from Base.get_auth import sign
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from Base.base_data import Setting as a

"""Base类封装常用方法给Page对象继承"""


class Base():

    def __init__(self, driver):
        self.driver = driver

    # ------------------------------------------------------通用基础方法----------------------------------------------------

    # 基础方法 -->定位单个元素
    def search_element(self, loc, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return: 返回定位对象
        """
        try:
            return WebDriverWait(self.driver, timeout, poll_frequency) \
                .until(lambda x: x.find_element(*loc))
        except:
            allure.attach("失败描述", "定位单个元素失败")
            return ""

    # 基础方法 -->定位一组元素
    def search_elements(self, loc, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return: 返回定位对象
        """
        try:
            return WebDriverWait(self.driver, timeout, poll_frequency) \
                .until(lambda x: x.find_elements(*loc))
        except:
            allure.attach("失败描述", "定位一组元素失败")
            return ""

    # 通过文本text -->定位单个元素
    def text_search_element(self, text):
        toast_xpath = "//*[starts-with(text(),'{}')]".format(text)
        return self.search_element((By.XPATH, toast_xpath), timeout=5, poll_frequency=1.0)

    # 通过文本text -->定位一组元素
    def text_search_elements(self, text):
        toast_xpath = "//*[starts-with(text(),'{}')]".format(text)
        return self.search_elements((By.XPATH, toast_xpath), timeout=5, poll_frequency=1.0)

    # 基础方法 -->点击单个元素
    def click_element(self, loc, timeout=15, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        self.search_element(loc, timeout, poll_frequency).click()

    # 通过文本text -->点击单个元素  //*[starts-with(text(),'客流识别机')]
    def text_click_element(self, text):
        sleep(1)
        toast_xpath = "//*[starts-with(text(),'{}')]".format(text)
        self.search_element((By.XPATH, toast_xpath), timeout=5, poll_frequency=1.0).click()

    # 通过index -->点击一组元素elements中单个元素【此方法可能会慢，慎用】
    def index_click_element(self, elements, index):
        sleep(1)
        values = self.search_elements(elements)
        sleep(1)
        for ind in range(0, len(values)):
            if index - 1 == ind:
                values[ind].click()
        sleep(2)

    # 基础方法 -->输入文本
    def send_element(self, loc, text, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param text: 输入文本内容
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        # 定位元素
        input_element = self.search_element(loc, timeout, poll_frequency)
        # 清空输入框
        input_element.clear()
        # 输入文本
        input_element.send_keys(text)

    # 键盘清空 -->输入文本
    def parm_send_element(self, loc, text, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param text: 输入文本内容
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        # 定位元素
        input_element = self.search_element(loc, timeout, poll_frequency)
        # 键盘输入Ctrl + a
        input_element.send_keys(Keys.CONTROL, 'a')
        # 键盘输入空格键，清空输入框内容
        input_element.send_keys(Keys.BACK_SPACE)
        # 输入内容
        input_element.send_keys(text)

    # 清空输入框
    def base_input_empty(self, loc, timeout=15, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param text: 输入文本内容
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        # 定位元素
        input_element = self.search_element(loc, timeout, poll_frequency)
        # 键盘输入Ctrl + a
        input_element.send_keys(Keys.CONTROL, 'a')
        # 键盘输入空格键，清空输入框内容
        input_element.send_keys(Keys.BACK_SPACE)

    # 上传一个文件
    def add_img(self, img_path, loc, timeout=10, poll_frequency=1.0):
        """
        :param img_path:上传文件路径及文件
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        self.search_element(loc, timeout, poll_frequency).send_keys(img_path)

    # 敲键盘的回车键
    def knock_enter_key(self, loc, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        self.search_element(loc, timeout, poll_frequency).send_keys(Keys.ENTER)

    # 敲键盘的ESC键
    def press_esc(self):
        win32api.keybd_event(27, 0, 0, 0)  # 键盘按ESE
        win32api.keybd_event(27, 0, win32con.KEYEVENTF_KEYUP, 0)  # 释放按键

    # 鼠标悬停
    def move_to_element(self, loc, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return:
        """
        ActionChains(self.driver).move_to_element(self.search_element(loc, timeout, poll_frequency)).perform()

    # 鼠标单击
    def mouse_xy_click(self, x, y):
        """
        :param x:页面x坐标值
        :param y:页面y坐标值
        """
        m = PyMouse()
        m.click(x, y)
        sleep(1)

    # 基础方法 -->返回单个元素文本值
    def get_text(self, loc, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return: 返回元素文本值
        """
        return self.search_element(loc, timeout, poll_frequency).text

    # 通过index -->返回一组元素loc中单个元素文本值【此方法可能会慢，慎用】
    def index_get_text(self, loc, index, timeout=10, poll_frequency=1.0):
        """
        :param loc: 元祖 定位类型 (By.ID,ID属性值) (By.CLASS_NAME,CLASS属性值) (By.XPATH,XPATH语句)
        :param Index: 元素所在下标+1
        :param timeout: 查找元素超时时间
        :param poll_frequency: 检测间隔时间，默认0.5s
        :return: 指定元素文本信息
        """
        all_text = self.search_elements(loc, timeout, poll_frequency)
        text_num = len(all_text)
        for i in range(0, text_num):
            if i + 1 == index:
                return all_text[i].text

    @allure.step(title="添加截图")
    def screen_page(self):
        try:
            # 截图文件名
            screen_name = "./Screen/ " + "%s--%s.png" % (time.strftime("%Y_%m_%d %H_%M_%S"), sys.exc_info()[1])
            # 截图操作
            self.driver.get_screenshot_as_file(screen_name)
            # 添加截图到报告
            allure.attach.file(screen_name, "%s--%s.png" % (time.strftime("%Y_%m_%d %H_%M_%S"), sys.exc_info()[1]),
                               attachment_type=allure.attachment_type.PNG)
        except Exception as e:
            allure.attach("错误描述", "添加截图错误,原因为：{}".format(e))

    # @allure.step(title="获取toast消息")
    def get_toast(self):
        try:
            page_toast = self.search_element(Page.com_page_toast, timeout=5, poll_frequency=0.5)
            page_toast = page_toast.get_attribute('innerHTML')
            allure.attach("实际返回toast结果", "{}".format(page_toast))
            return page_toast
        except Exception as e:
            allure.attach("错误描述", "获取toast消息错误,原因为：{}".format(e))
            # 截图
            self.screen_page()
            return ""

    # @allure.step(title="获取toast消息")
    def get_ota_toast(self):
        try:
            page_toast = self.search_element(Page.com_page_toast, timeout=100, poll_frequency=0.5)
            page_toast = page_toast.get_attribute('innerHTML')
            allure.attach("实际返回toast结果", "{}".format(page_toast))
            return page_toast
        except Exception as e:
            allure.attach("错误描述", "获取toast消息错误,原因为：{}".format(e))
            # 截图
            self.screen_page()
            return ""

    @allure.step(title="关闭浏览器操作")
    def base_close_browser(self):
        self.driver.quit()

    # @allure.step(title="刷新页面")
    def refresh_current_page(self):
        self.driver.refresh()
        sleep(3)

    @allure.step(title="关闭弹框")
    def close_message_box(self):
        try:
            sleep(2)
            # 关闭弹框
            self.click_element(Page.com_close_box_btn)
        except:
            # 页面刷新
            self.refresh_current_page()
            allure.attach("错误描述", "关闭弹框错误,已刷新页面")

    # 操作滚动条
    def base_scroll_to(self, x, y):
        self.driver.execute_script("window.scrollTo({}, {})".format(x, y))

    # 获取网页title
    def com_get_title(self):
        return self.driver.title

    # 切换frame表单方法
    def base_switch_frame(self, ele):
        self.driver.switch_to.frame(self.search_element(ele))

    #  切换至默认frame表单方法
    def base_switch_default_frame(self):
        self.driver.switch_to.default_content()

    # 回到默认frame表单方法
    def base_switch_default(self):
        self.driver.switch_to.default_content()

    # 切换窗口方法
    def base_switch_window(self, title):
        self.driver.switch_to.window(self.base_title_get_handle(title))

    # 根据title 获取指定窗口句柄
    def base_title_get_handle(self, title):
        # 遍历 所有窗口句柄
        for handle in self.driver.window_handles:
            # 切换 窗口 目的：获取当前窗口title
            self.driver.switch_to.window(handle)
            # 判断当前窗口title 是否 等于指定要获取的title
            if self.driver.title == title:
                # 条件成立 返回当前窗口 句柄
                return handle

    # ------------------------------------------------------基础断言方法----------------------------------------------------

    # 判断元素是否存在
    def base_element_is_exist(self, loc):
        if self.search_element(loc, timeout=5) == "":
            return False  # 代表元素不存在
        else:
            return True  # 代表元素不存在

    # 断言 -->actual结果 == expect结果 【所有断言两个值相等使用】
    @allure.step("断言actual结果 == expect结果")
    def base_if_equal(self, actual, expect):
        try:
            if actual == expect:
                allure.attach("断言描述", '断言成功：实际结果[{f}]，预期结果[{s}]'.format(f=actual, s=expect))
                return True
            else:
                allure.attach("断言描述", '断言失败：实际结果[{f}]，预期结果[{s}]'.format(f=actual, s=expect))
                return False
        except Exception as e:
            allure.attach("失败描述", '断言两值相等错误，原因：{}'.format(e))
            assert False

    # 断言 -->keyword in expect结果 【所有断言包含使用】
    @allure.step("断言keyword in expect结果")
    def base_if_include(self, keyword, expect):
        try:
            if str(keyword) in str(expect):
                allure.attach("断言描述", '断言成功：实际结果[{f}]，预期结果[{s}]'.format(f=keyword, s=expect))
                return True
            else:
                allure.attach("断言描述", '断言失败：实际结果[{f}]，预期结果[{s}]'.format(f=keyword, s=expect))
                return False
        except Exception as e:
            allure.attach("失败描述", '断言keyword in expect结果错误，原因：{}'.format(e))
            assert False

    # ---------------------------------------------------接口方法阈值前置使用-----------------------------------------------

    # 发送POST请求  -->openapi接口使用
    @allure.step(title="发送POST请求")
    def http_post(self, path, method, body=None):
        try:
            dic = body or {}
            data = json.dumps(dic)
            data = bytes(data, 'utf-8')  # 格式化请求体
            headers = {"host": a.api_test, "Content-Type": "application/json"}
            authorization = sign(a.ak, a.sk, method, path, None, headers)
            headers["authorization"] = authorization
            headers["Method"] = method
            url = "http://" + a.api_test + path  # 请求url
            res = requests.post(url=url, headers=headers, data=data)  # 发送请求
            return res
        except Exception as e:
            allure.attach('错误描述', 'POST请求出错，出错原因:%s' % e)

    # -----------------------------------------------------多个页面共同使用方法----------------------------------------------

    @allure.step(title="点击 确定")
    def page_click_accept(self):
        sleep(1)
        self.click_element(Page.app_accept_btn)

    @allure.step(title="点击 确定")
    def page_click_com_accept(self):
        sleep(1)
        self.click_element(Page.com_accept_btn)

    @allure.step(title="点击 取消")
    def page_click_cancel(self):
        sleep(1)
        self.click_element(Page.app_cancel_btn)

    @allure.step(title="点击 取消")
    def page_click_com_cancel(self):
        sleep(1)
        self.click_element(Page.com_cancel_btn)

    @allure.step(title="选择 机型选项")
    def page_sel_type_option(self, option_text):
        # 选择全部
        sleep(2)
        if option_text == "全部":
            self.click_element(Page.com_option_first)
        else:
            # 选择机型（"客流机"/"抓拍机"/"识别机"）
            all_option = self.search_elements(Page.com_options)
            for i in range(0, len(all_option)):
                if option_text == all_option[i].text:
                    all_option[i].click()
                    sleep(3)
        allure.attach("描述", "选择设备类型为：{}".format(option_text))
